/*
 * instructions.c
 *
 *  Created on: 03/10/2013
 *      Author: vitor
 */
#include "instructions.h"

// Big-endian
void storeWord(vmInfo* vm, unsigned int position, unsigned int bytes) {
	if (position < MEM_SIZE) {
		vm->mem[position] = (bytes & 0xFF000000) >> 24;
		vm->mem[position + 1] = (bytes & 0x00FF0000) >> 16;
		vm->mem[position + 2] = (bytes & 0x0000FF00) >> 8;
		vm->mem[position + 3] = (bytes & 0x000000FF);
	} else {
		fprintf(stderr,
				"ERRO! storeWord sendo chamado com posicao maior que memoria: %d\n",
				position);
		exit(1);
	}
}

unsigned int loadWord(vmInfo* vm, unsigned int position) {
	unsigned int bytes;

	if (position < MEM_SIZE) {
		bytes = vm->mem[position];
		bytes = bytes << 8 | vm->mem[position + 1];
		bytes = bytes << 8 | vm->mem[position + 2];
		bytes = bytes << 8 | vm->mem[position + 3];

		return bytes;
	} else {
		fprintf(stderr,
				"ERRO! loadWord sendo chamado com posicao maior que memoria: %d\n",
				position);
		exit(1);
	}
}

void pushWord(vmInfo* vm, unsigned int bytes) {
	vm->reg[SP] -= 4;
	storeWord(vm, vm->reg[SP], bytes);
}

unsigned int popWord(vmInfo* vm) {
	unsigned int bytes;

	bytes = loadWord(vm, vm->reg[SP]);
	vm->reg[SP] += 4;

	return bytes;
}

void nop(vmInfo* vm, instrInfo inst) {
}

void stop(vmInfo* vm, instrInfo inst) {
	vm->running = 0;
}

void and(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] & vm->reg[inst.rt];
}

void or(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] | vm->reg[inst.rt];
}

void nor(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = ~(vm->reg[inst.rs] | vm->reg[inst.rt]);
}

void add(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] + vm->reg[inst.rt];
}

void sub(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] - vm->reg[inst.rt];
}

void mult(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] * vm->reg[inst.rt];
}

void divide(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] / vm->reg[inst.rt];
}

void rem(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rd] = vm->reg[inst.rs] % vm->reg[inst.rt];
}

void cmp(vmInfo* vm, instrInfo inst) {
	unsigned int newFlag = 0;

	if (vm->reg[inst.rs] == vm->reg[inst.rt]) {
		newFlag |= 1;
	}
	if (vm->reg[inst.rs] < vm->reg[inst.rt]) {
		newFlag |= 2;
	}
	if (vm->reg[inst.rs] > vm->reg[inst.rt]) {
		newFlag |= 4;
	}

	vm->cmpFlags = newFlag;
}

void jeq(vmInfo* vm, instrInfo inst) {
	if (vm->cmpFlags & 1) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jne(vmInfo* vm, instrInfo inst) {
	if (!(vm->cmpFlags & 1)) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jlt(vmInfo* vm, instrInfo inst) {
	if (vm->cmpFlags & 2) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jgt(vmInfo* vm, instrInfo inst) {
	if (vm->cmpFlags & 4) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jle(vmInfo* vm, instrInfo inst) {
	if ((vm->cmpFlags & 1) || (vm->cmpFlags & 2)) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jge(vmInfo* vm, instrInfo inst) {
	if ((vm->cmpFlags & 1) || (vm->cmpFlags & 4)) {
		vm->pc = (unsigned short) inst.imm;
	}
}

void jmp(vmInfo* vm, instrInfo inst) {
	vm->pc = (unsigned short) inst.imm;
}

void call(vmInfo* vm, instrInfo inst) {
	pushWord(vm, vm->pc);
	vm->pc = (unsigned short) inst.imm;
}

void ret(vmInfo* vm, instrInfo inst) {
	vm->pc = popWord(vm);
}

void load(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = loadWord(vm, vm->reg[inst.rs] + inst.imm);
}

void store(vmInfo* vm, instrInfo inst) {
	storeWord(vm, vm->reg[inst.rs] + inst.imm, vm->reg[inst.rt]);
}

void addi(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = vm->reg[inst.rs] + inst.imm;
}

void syscall(vmInfo* vm, instrInfo inst) {
	unsigned int operation;
	int value;

	pushWord(vm, vm->pc);
	pushWord(vm, vm->reg[BP]);
	vm->reg[BP] = vm->reg[SP];

	operation = loadWord(vm, vm->reg[BP] + 8);

	switch (operation) {
	case 0:	// Print int
		value = loadWord(vm, vm->reg[BP] + 12);
		printf("%d", value);
		break;
	case 1:	// Print string
		value = loadWord(vm, vm->reg[BP] + 12);
		printf("%s", &vm->mem[value]);
		break;
	case 2:	// Read int
		scanf("%d", &vm->reg[RV]);
		break;
	default:
		fprintf(stderr, "ERRO! Syscall: operacao desconhecida: %d\n",
				operation);
		exit(1);
		break;
	}
	fflush(stdout);

	vm->reg[SP] = vm->reg[BP];
	vm->reg[BP] = popWord(vm);
	ret(vm, inst);
}

void push(vmInfo* vm, instrInfo inst) {
	pushWord(vm, vm->reg[inst.rs]);
}

void pop(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = popWord(vm);
}

void lui(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = (inst.imm << 16);
}

void ori(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = vm->reg[inst.rs] | (unsigned short) inst.imm;
}

void sll(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = vm->reg[inst.rs] << inst.imm;
}

void srl(vmInfo* vm, instrInfo inst) {
	unsigned int toShift = (unsigned int) vm->reg[inst.rs];
	vm->reg[inst.rt] = toShift >> inst.imm;
}

void sra(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = vm->reg[inst.rs] >> inst.imm;
}

void pushi(vmInfo* vm, instrInfo inst) {
	pushWord(vm, inst.imm);
}

void addiu(vmInfo* vm, instrInfo inst) {
	vm->reg[inst.rt] = vm->reg[inst.rs] + (unsigned short) inst.imm;
}

// Usa funct para determinar qual instrução será executada
void (*RInstrArray[])(vmInfo* vm, instrInfo inst) = {
	and,	// 000000
		or,// 000001
		nor,// 000010
		add,// 000011
		sub,// 000100
		mult,// 000101
		divide,// 000110
		rem// 000111
};

// Usa opcode para determinar qual instrução será executada
void (*IInstrArray[])(vmInfo* vm, instrInfo inst) = {
	nop,	// Nunca é chamado
		stop,// 000001
		cmp,// 000010
		jeq,// 000011
		jne,// 000100
		jlt,// 000101
		jgt,// 000110
		jle,// 000111
		jge,// 001000
		jmp,// 001001
		call,// 001010
		ret,// 001011
		load,// 001100
		store,// 001101
		addi,// 001110
		syscall,// 001111
		push,// 010000
		pop,// 010001
		lui,// 010010
		ori,// 010011
		sll,// 010100
		srl,// 010101
		sra,// 010110
		pushi,// 010111
		addiu// 011000
};
